/**
 * Classe chargée de la génération du code en rapport avec les expressions
 * 
 * @author BlobbyCompiloTeam
 *
 */

import java.util.Stack;

public class GenerationExpression implements GramConstants{

	private Stack<Integer> pileType; // Pile des types, mise à jour au fur et à mesure de l'évaluation
	private Stack<Integer> pileOp; // La pile des opérateurs, mise à jour au fur et à mesure de l'évaluation

	private static MaP map; // Référence vers la machine à pile pour générer le code ASM ou YVM

	
	/**
	 * Constructeur de la classe GenerationExpression
	 * @param _map Référence vers la machine à pile qui génère le code ASM ou YVM
	 */
	public GenerationExpression(MaP _map){
		map = _map;

		pileType = new Stack<Integer>();
		pileOp = new Stack<Integer>();
	}


	/**
	 * Empile sur la pile de vérification des types un identifiant selon sa nature et son type
	 * @param id référence vers l'identifiant à empiler
	 */
	public void emp_ident(Ident id)
	{
		int nat = id.getNature();
		int type = id.getType();
		if(nat == natVar){ // nature Variable
			emp_var(((IdVar)(id)));
		}
		if(nat == natPara){ //nature Parametre
			emp_varPara(((IdPara)(id)));
		}
		if(nat == natConst){ // nature Constante
			if (type == typeEnt || type == typeErreur){ // Entier
				emp_val(((IdConst)(id)).getValeur());
			}
			if (type == typeBool){ //Boolean
				emp_bool(((IdConst)(id)).getValeur());
			}
		}
		
	}
	
	/**
	 * Empile sur la pile de vérification des types un paramètre et génère le code dans le fichier de sortie
	 * @param id La référence vers l'identifiant à empiler
	 */
	private void emp_varPara(IdPara id){
		map.fonc_emp_vparf(id.getOffset());
		pileType.push(id.getType());
	}
	
	
	/**
	 * Empile une valeur entière sur la pile de vérification des types
	 * @param val la valeur entière à empiler
	 */
	public void emp_val(int val){
		map.emp_val(val);
		pileType.push(typeEnt);
	}
	
	/**
	 * Empile une valeur booléenne sur la pile de vérification des types
	 * @param val valeur booléenne à empiler
	 */
	public void emp_bool(int val){
		map.emp_val(val);
		pileType.push(typeBool);
	}	
	
	/**
	 * Empile une variable sur la pile de vérification des types
	 * @param référence vers l'identifiant de la variable à empiler 
	 */
	private void emp_var(IdVar id){
		map.emp_var(id.getOffset());
		pileType.push(id.getType());
	}
	
	/**
	 * Empile un type de retour d'une fonction dans la pile de vérification des types
	 * @param type le type de retour de la fonction à empiler
	 */
	public void typeFonct(int type){
		pileType.push(type);
		
	}
	
	/**
	 * Empile un opérateur addition dans la pile de vérification des opérateurs
	 */
	public void add(){
		pileOp.push(opPlus);
	}

	
	/**
	 * Empile un opérateur de soustraction dans la pile de vérification des opérateurs
	 */
	public void sous(){
		pileOp.push(opMoins);
	}
	
	/**
	 * Empile un opérateur de multiplication dans la pile de vérification des opérateurs
	 */
	public void mul(){
		pileOp.push(opMul);
	}
	
	/**
	 * Empile un opérateur de division dans la pile de vérification des opérateurs
	 */
	public void div(){
		pileOp.push(opDiv);
	}
	
	/**
	 * Empile un opérateur ou dans la pile de vérification des opérateurs
	 */
	public void ou(){
		pileOp.push(opOU);
	}
	
	/**
	 * Empile un opérateur et dans la pile de vérification des opérateurs
	 */
	public void et(){
		pileOp.push(opET);
	}
	
	/**
	 * Empile un opérateur inférieur dans la pile de vérification des opérateurs
	 */
	public void inf(){
		pileOp.push(opInf);
	}
	
	/**
	 * Empile un opérateur inférieur ou égal dans la pile de vérification des opérateurs
	 */
	public void infeg(){
		pileOp.push(opInfEg);
	}
	
	/**
	 * Empile un opérateur supérieur dans la pile de vérification des opérateurs
	 */
	public void sup(){
		pileOp.push(opSup);
	}
	
	/**
	 * Empile un opérateur supérieur ou égal dans la pile de vérification des opérateurs
	 */
	public void supeg(){
		pileOp.push(opSupEg);
	}
	
	/**
	 * Empile un opérateur égal dans la pile de vérification des opérateurs
	 */
	public void eg(){
		pileOp.push(opEg);
	}
	
	/**
	 * Empile un opérateur différent dans la pile de vérification des opérateurs
	 */
	public void dif(){
		pileOp.push(opDif);
	}
	
	/**
	 * Empile un opérateur non dans la pile de vérification des opérateurs
	 */
	public void non() {
		pileOp.push(opNON);
	}
	
	
	/**
	 * Effectue le contrôle de type de l'opération courante dans la pile.
	 * Et empile le type correspondant au résultat le cas échant. Génère le code correspondant dans le fichier de sortie.
	 */
	public void operation(){

		int a = pileType.pop();
		int op = pileOp.pop();
		int type = typeBool;
		boolean estCorrect = false;
		
		if(op == opNON) {
			estCorrect = ControleType.controleUnaire(a, op);
			map.non();
			pileType.push(type); 
		} else {
			int b = pileType.pop();
			switch (op){
				case opMoins :
					map.sous();
					type = typeEnt;
					break;
				case opPlus :
					map.add();
					type = typeEnt;
					break;
				case opMul :
					map.mul();
					type = typeEnt;
					break;
				case opDiv :
					map.div();
					type = typeEnt;
					break;
				case opOU:
					map.ou();
					type = typeBool;
					break;
				case opInf :
					map.inf();
					type = typeBool;
					break;
				case opSup :
					map.sup();
					type = typeBool;
					break;
				case opInfEg :
					map.infeq();
					type = typeBool;
					break;
				case opSupEg :
					map.supeq();
					type = typeBool;
					break;
				case opET :
					map.et();
					type = typeBool;
					break;
				case opEg :
					map.eq();
					type = typeBool;
					break;
				case opDif :
					map.neq();
					type = typeBool;
					break;
			}
		
			if (ControleType.controleBinaire(a,b,op)){
				pileType.push(type); 
				estCorrect = true;
			}
		}
		
		if(!estCorrect){
			pileType.push(typeErreur);
		}
		
	}
	
	/**
	 * Fonction permettant de vérifier que la pile des opérateurs et la pile des type sont vides.
	 * Si elles ne le sont pas : affiche un erreur, les vide pour la compilation de la prochaine expression et retourne un type Erreur
	 * @return le type final de l'expression, ou le type Erreur si l'expression est erronée.
	 */
	public int finExpr(){
		while(!pileOp.empty()){
			pileOp.pop();
		}
		if (!pileType.empty()){
		
			if(pileType.peek() == typeErreur){
				MsgErreur.getErreur(Gram.token.beginLine, "Erreur dans l'expression !");			
			}
			int der = pileType.pop();
			while(!pileType.empty()){
				pileType.pop();
			}

			return der;
			
		}
		return typeErreur;
	}
	
	/**
	 * Permet de dépiler le type en sommet de la pile de type
	 * @return la constante de type dépilée
	 */
	public int depileType() {
		return pileType.pop();
	}

	
}
